package com.echartsdata.jwt;

import com.echartsdata.demos.web.JwtAuthenticationController;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.jsonwebtoken.ExpiredJwtException;
import io.jsonwebtoken.MalformedJwtException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.MediaType;
import org.springframework.security.authentication.AuthenticationServiceException;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.security.SignatureException;
import java.util.HashMap;
import java.util.Map;

/**
 * JWT认证失败处理入口点
 * 实现Spring Security的AuthenticationEntryPoint接口，用于处理所有未授权访问的情况
 * 当用户访问需要认证的资源但未通过身份验证时，Spring Security会调用此类的commence方法
 * 作用：统一返回JSON格式的未授权错误信息，包含具体的错误原因（如令牌过期、无效令牌等）
 */
@Component
public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint {
    private static final Logger logger = LoggerFactory.getLogger(JwtAuthenticationEntryPoint.class);
    /**
     * 处理未授权访问的核心方法
     * 当认证失败（如令牌无效、过期、未携带令牌等）时，被Spring Security框架自动调用
     *
     * @param request       当前请求对象，包含请求路径、参数等信息
     * @param response      响应对象，用于设置响应状态码、内容类型和返回数据
     * @param authException 认证失败时抛出的异常，包含失败原因
     * @throws IOException  输入输出异常（如写入响应流失败时抛出）
     */
    @Override
    public void commence(HttpServletRequest request,
                         HttpServletResponse response,
                         AuthenticationException authException) throws IOException {
        // 1. 设置响应状态码为401（未授权）
        response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
        // 2. 设置响应内容类型为JSON，编码为UTF-8（确保中文正常显示）
        response.setContentType(MediaType.APPLICATION_JSON_VALUE);
        response.setCharacterEncoding("UTF-8");

        // 3. 构建错误响应的JSON数据
        Map<String, Object> body = new HashMap<>();
        body.put("status", HttpServletResponse.SC_UNAUTHORIZED); // 状态码：401
        body.put("error", "没有访问权限！拒绝访问。"); // 错误类型描述
        // 4. 根据异常类型获取具体的错误消息（调用工具方法）
        String message = getDetailedErrorMessage(authException);
        body.put("message", message); // 详细错误原因
        body.put("path", request.getRequestURI()); // 记录请求的路径，方便排查问题

        // 5. 使用Jackson的ObjectMapper将Map转换为JSON字符串，并写入响应流
        ObjectMapper mapper = new ObjectMapper();
        mapper.writeValue(response.getOutputStream(), body);
    }

    /**
     * 根据不同的认证异常类型，返回具体的中文错误消息
     * 用于细化错误原因，帮助前端明确提示用户（如令牌过期、无效令牌等）
     *
     * @param ex 认证失败时的异常对象，包含异常类型和原因
     * @return 具体的中文错误消息
     */
    private String getDetailedErrorMessage(AuthenticationException ex) {
        // 获取原始异常（可能是JWT相关的具体异常，如ExpiredJwtException）
        Throwable cause = ex.getCause();

        // 1. 处理JWT令牌过期的情况
        if (cause instanceof ExpiredJwtException) {
        logger.error("令牌已过期");
            return "令牌已过期";
        }
        // 2. 处理签名错误或令牌格式错误的情况（无效令牌）
        else if (cause instanceof SignatureException || cause instanceof MalformedJwtException) {
            logger.error("无效的令牌");
            return "无效的令牌";
        }
        // 3. 处理认证凭证无效的情况（如密码错误）
        else if (ex instanceof BadCredentialsException) {
            logger.error("无效的认证凭证");
            return "无效的认证凭证";
        }
        // 4. 处理自定义的认证服务异常（如JWT验证过程中的自定义错误）
        else if (ex instanceof AuthenticationServiceException) {
            return ex.getMessage(); // 直接使用异常中携带的自定义消息
        }
        // 5. 其他未明确的认证失败情况（默认提示）
        else {
            return "需要完整的身份验证才能访问此资源";
        }
    }
}